package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.*;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.*;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private HotelMapper hotelMapper;

    @Override
    public PageResult queryHotel(RequestParams requestParams) {
        try {
            //1. 准备request
            SearchRequest request = new SearchRequest("hotel");
            //2. 准备DSL
            buildBoolQuery(requestParams, request);
            //3. 发送请求
            SearchResponse search = client.search(request, RequestOptions.DEFAULT);
            //4. 解析请求返回。
            return handlResponse(search);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void buildBoolQuery(RequestParams requestParams, SearchRequest request) {
        //2. 准备DSL
        BoolQueryBuilder boolQuery = new BoolQueryBuilder();

        String key = requestParams.getKey();
        //全文查询
        if(StringUtils.isNotBlank(key)){
            boolQuery.must(QueryBuilders.matchQuery("all",key));
        }else{
            boolQuery.must(QueryBuilders.matchAllQuery());
        }
        //城市
        if(StringUtils.isNotBlank(requestParams.getCity())){
            boolQuery.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
        }
        //品牌
        if(StringUtils.isNotBlank(requestParams.getBrand())){
            boolQuery.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
        }
        //星级
        if(StringUtils.isNotBlank(requestParams.getStarName())){
            String starName = requestParams.getStarName();
            if(requestParams.getStarName().equals("四星") || requestParams.getStarName().equals("五星")){
                starName += "级";
            }
            boolQuery.filter(QueryBuilders.termQuery("starName",starName));
        }
        //价格
        if(requestParams.getMaxPrice() != null && requestParams.getMinPrice() != null){
            boolQuery.must(
                    QueryBuilders.rangeQuery("price")
                            .gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
        }


        //2.1 分页
        int page = requestParams.getPage();
        int size = requestParams.getSize();
        request.source().from( (page-1)*size ).size( size );

        // 排序
        if(StringUtils.isNotBlank(requestParams.getLocation())){
            // 距离点 排序
            request.source().sort(SortBuilders
                    .geoDistanceSort("location",new GeoPoint(requestParams.getLocation()))
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS)
            );
        }

        // 查询算分控制
        FunctionScoreQueryBuilder functionScoreQueryBuilder =
                QueryBuilders.functionScoreQuery(
                        //原始查询，相关性算分的查询
                        boolQuery,
                        // function score的数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                //数组中的 一个 function soure 元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        //过滤条件
                                        QueryBuilders.termQuery("isAD",true),
                                        //算分函数
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        }
                );
        request.source().query(functionScoreQueryBuilder);
    }

    private PageResult handlResponse(SearchResponse search) {
        //查询结果解析
        SearchHits searchHits = search.getHits();

        long total = searchHits.getTotalHits().value;
        SearchHit[] hits = searchHits.getHits();

        if(hits != null && hits.length > 0){
            List<HotelDoc> hotels = new ArrayList<>();
            for (SearchHit hit : hits) {
                String json = hit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

                Object[] sortValues = hit.getSortValues();
                if(sortValues != null && sortValues.length > 0){
                    Object sortValue = sortValues[0];
                    hotelDoc.setDistance(sortValue);
                }
                hotels.add(hotelDoc);
            }


            return new PageResult(total,hotels);
        }
        return null;
    }


    @Override
    public Map<String, List<String>> filters(RequestParams requestParams) {
        try {
            //声明请求
            SearchRequest request = new SearchRequest("hotel");
            //设置DSL
            request.source().size(0);
            //2. 准备DSL
            buildBoolQuery(requestParams, request);
            //设置聚合桶
            buildAggregation(request);
            //发起请求
            SearchResponse search = client.search(request, RequestOptions.DEFAULT);

            //声明MAP
            Map<String, List<String>> resMap = new HashMap<>();

            Aggregations aggregations = search.getAggregations();
            List<String> cityAggList = buildRes(aggregations, "cityAgg");
            resMap.put("city",cityAggList);
            List<String> brandAggList = buildRes(aggregations, "brandAgg");
            resMap.put("brand",brandAggList);
            List<String> starAggList = buildRes(aggregations, "starAgg");
            resMap.put("starName",starAggList);

            return resMap;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<String> getSuggestion(String prefix) {
        try {
            //定义请求
            SearchRequest request = new SearchRequest("hotel");
            //定义 DSL
            request.source().suggest(new SuggestBuilder().addSuggestion(
                    //自定义suggest 名
                    "suggestions",
                    SuggestBuilders                         //对应的文档字段名
                            .completionSuggestion("suggestion")
                            .prefix(prefix) //检索文字
                            .skipDuplicates(true) //去重
                            .size(10)
            ));
            //发送请求
            SearchResponse search = client.search(request, RequestOptions.DEFAULT);
            //解析结果
            Suggest suggest = search.getSuggest();
            //获取suggestion
            CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
            //获取options
            List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();

            List<String> list = new ArrayList<>(options.size());
            for (CompletionSuggestion.Entry.Option option : options) {
                String s = option.getText().toString();
                list.add(s);
            }
            System.out.println(list);
            return list;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



    private List<String> buildRes(Aggregations aggregations,String termName) {

        Terms term  = aggregations.get(termName);
        List<? extends Terms.Bucket> buckets = term.getBuckets();
        List<String> stringList = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            String keyAsString = bucket.getKeyAsString();
            stringList.add(keyAsString);
        }
        return stringList;
    }

    private void buildAggregation(SearchRequest request) {
        request.source().aggregation(AggregationBuilders
                .terms("cityAgg")
                .size(100)
                .field("city")
        );
        request.source().aggregation(AggregationBuilders
                .terms("brandAgg")
                .size(100)
                .field("brand")
        );
        request.source().aggregation(AggregationBuilders
                .terms("starAgg")
                .field("starName")
                .size(100)
        );
    }

    /**
     * @Author dy
     * @Description: 监听 hotel 队列 发送的新增，修改
     */
    @Override
    public void insertById(Long id) {
        try {
            //从数据库 查询数据
            Hotel hotel = hotelMapper.selectById(id);
            // 转为 HotelDec
            HotelDoc hotelDoc = new HotelDoc(hotel);

            //准备request请求
            IndexRequest indexRequest = new IndexRequest("hotel").id(hotelDoc.getId().toString());
            // DSL
            indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            client.index(indexRequest,RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @Author dy
     * @Description: 监听 hotel 删除队列 发送的消息
     */
    @Override
    public void deleteById(Long id) {
        try {
            DeleteRequest query = new DeleteRequest("hotel",id.toString());
            client.delete(query,RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object findHotelById(Long id) {

        Hotel hotel = hotelMapper.selectById(id);

        return hotel;
    }


}
